function unserialize(strings) {
    var _slicedToArray = function () {
        function sliceIterator(arr, i) {
            var _arr = [];
            var _n = true;
            var _d = false;
            var _e = undefined;
            try {
                for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
                    _arr.push(_s.value);
                    if (i && _arr.length === i) break;
                }
            } catch (err) {
                _d = true;
                _e = err;
            } finally {
                try {
                    if (!_n && _i["return"]) _i["return"]();
                } finally {
                    if (_d) throw _e;
                }
            }
            return _arr;
        }

        return function (arr, i) {
            if (Array.isArray(arr)) {
                return arr;
            } else if (Symbol.iterator in Object(arr)) {
                return sliceIterator(arr, i);
            } else {
                throw new TypeError("Invalid attempt to destructure non-iterable instance");
            }
        };
    }();

    function initCache() {
        var store = [];
        // cache only first element, second is length to jump ahead for the parser
        var cache = function cache(value) {
            store.push(value[0]);
            return value;
        };

        cache.get = function (index) {
            if (index >= store.length) {
                throw RangeError('Can\'t resolve reference ' + (index + 1));
            }

            return store[index];
        };

        return cache;
    }

    function expectType(str, cache) {
        var types = /^(?:N(?=;)|[bidsSaOCrR](?=:)|[^:]+(?=:))/g;
        var type = (types.exec(str) || [])[0];

        if (!type) {
            throw SyntaxError('Invalid input: ' + str);
        }

        switch (type) {
            case 'N':
                return cache([null, 2]);
            case 'b':
                return cache(expectBool(str));
            case 'i':
                return cache(expectInt(str));
            case 'd':
                return cache(expectFloat(str));
            case 's':
                return cache(expectString(str));
            case 'S':
                return cache(expectEscapedString(str));
            case 'a':
                return expectArray(str, cache);
            case 'O':
                return expectObject(str, cache);
            case 'C':
                return expectClass(str, cache);
            case 'r':
            case 'R':
                return expectReference(str, cache);
            default:
                throw SyntaxError('Invalid or unsupported data type: ' + type);
        }
    }

    function expectBool(str) {
        var reBool = /^b:([01]);/;

        var _ref = reBool.exec(str) || [],
            _ref2 = _slicedToArray(_ref, 2),
            match = _ref2[0],
            boolMatch = _ref2[1];

        if (!boolMatch) {
            throw SyntaxError('Invalid bool value, expected 0 or 1');
        }

        return [boolMatch === '1', match.length];
    }

    function expectInt(str) {
        var reInt = /^i:([+-]?\d+);/;

        var _ref3 = reInt.exec(str) || [],
            _ref4 = _slicedToArray(_ref3, 2),
            match = _ref4[0],
            intMatch = _ref4[1];

        if (!intMatch) {
            throw SyntaxError('Expected an integer value');
        }

        return [parseInt(intMatch, 10), match.length];
    }

    function expectFloat(str) {
        var reFloat = /^d:(NAN|-?INF|(?:\d+\.\d*|\d*\.\d+|\d+)(?:[eE][+-]\d+)?);/;

        var _ref5 = reFloat.exec(str) || [],
            _ref6 = _slicedToArray(_ref5, 2),
            match = _ref6[0],
            floatMatch = _ref6[1];

        if (!floatMatch) {
            throw SyntaxError('Expected a float value');
        }

        var floatValue = void 0;

        switch (floatMatch) {
            case 'NAN':
                floatValue = Number.NaN;
                break;
            case '-INF':
                floatValue = Number.NEGATIVE_INFINITY;
                break;
            case 'INF':
                floatValue = Number.POSITIVE_INFINITY;
                break;
            default:
                floatValue = parseFloat(floatMatch);
                break;
        }

        return [floatValue, match.length];
    }

    function readBytes(str, len) {
        var escapedString = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;

        var bytes = 0;
        var out = '';
        var c = 0;
        var strLen = str.length;
        var wasHighSurrogate = false;
        var escapedChars = 0;

        while (bytes < len && c < strLen) {
            var chr = str.charAt(c);
            var code = chr.charCodeAt(0);
            var isHighSurrogate = code >= 0xd800 && code <= 0xdbff;
            var isLowSurrogate = code >= 0xdc00 && code <= 0xdfff;

            if (escapedString && chr === '\\') {
                chr = String.fromCharCode(parseInt(str.substr(c + 1, 2), 16));
                escapedChars++;

                // each escaped sequence is 3 characters. Go 2 chars ahead.
                // third character will be jumped over a few lines later
                c += 2;
            }

            c++;

            bytes += isHighSurrogate || isLowSurrogate && wasHighSurrogate
                // if high surrogate, count 2 bytes, as expectation is to be followed by low surrogate
                // if low surrogate preceded by high surrogate, add 2 bytes
                ? 2 : code > 0x7ff
                    // otherwise low surrogate falls into this part
                    ? 3 : code > 0x7f ? 2 : 1;

            // if high surrogate is not followed by low surrogate, add 1 more byte
            bytes += wasHighSurrogate && !isLowSurrogate ? 1 : 0;

            out += chr;
            wasHighSurrogate = isHighSurrogate;
        }

        return [out, bytes, escapedChars];
    }

    function expectString(str) {
        // PHP strings consist of one-byte characters.
        // JS uses 2 bytes with possible surrogate pairs.
        // Serialized length of 2 is still 1 JS string character
        var reStrLength = /^s:(\d+):"/g; // also match the opening " char

        var _ref7 = reStrLength.exec(str) || [],
            _ref8 = _slicedToArray(_ref7, 2),
            match = _ref8[0],
            byteLenMatch = _ref8[1];

        if (!match) {
            throw SyntaxError('Expected a string value');
        }

        var len = parseInt(byteLenMatch, 10);

        str = str.substr(match.length);

        var _readBytes = readBytes(str, len),
            _readBytes2 = _slicedToArray(_readBytes, 2),
            strMatch = _readBytes2[0],
            bytes = _readBytes2[1];

        if (bytes !== len) {
            throw SyntaxError('Expected string of ' + len + ' bytes, but got ' + bytes);
        }

        str = str.substr(strMatch.length);

        // strict parsing, match closing "; chars
        if (!str.startsWith('";')) {
            throw SyntaxError('Expected ";');
        }

        return [strMatch, match.length + strMatch.length + 2]; // skip last ";
    }

    function expectEscapedString(str) {
        var reStrLength = /^S:(\d+):"/g; // also match the opening " char

        var _ref9 = reStrLength.exec(str) || [],
            _ref10 = _slicedToArray(_ref9, 2),
            match = _ref10[0],
            strLenMatch = _ref10[1];

        if (!match) {
            throw SyntaxError('Expected an escaped string value');
        }

        var len = parseInt(strLenMatch, 10);

        str = str.substr(match.length);

        var _readBytes3 = readBytes(str, len, true),
            _readBytes4 = _slicedToArray(_readBytes3, 3),
            strMatch = _readBytes4[0],
            bytes = _readBytes4[1],
            escapedChars = _readBytes4[2];

        if (bytes !== len) {
            throw SyntaxError('Expected escaped string of ' + len + ' bytes, but got ' + bytes);
        }

        str = str.substr(strMatch.length + escapedChars * 2);

        // strict parsing, match closing "; chars
        if (!str.startsWith('";')) {
            throw SyntaxError('Expected ";');
        }

        return [strMatch, match.length + strMatch.length + 2]; // skip last ";
    }

    function expectKeyOrIndex(str) {
        try {
            return expectString(str);
        } catch (err) {
        }

        try {
            return expectEscapedString(str);
        } catch (err) {
        }

        try {
            return expectInt(str);
        } catch (err) {
            throw SyntaxError('Expected key or index');
        }
    }

    function expectObject(str, cache) {
        // O:<class name length>:"class name":<prop count>:{<props and values>}
        // O:8:"stdClass":2:{s:3:"foo";s:3:"bar";s:3:"bar";s:3:"baz";}
        var reObjectLiteral = /^O:(\d+):"([^"]+)":(\d+):\{/;

        var _ref11 = reObjectLiteral.exec(str) || [],
            _ref12 = _slicedToArray(_ref11, 4),
            objectLiteralBeginMatch = _ref12[0],
            /* classNameLengthMatch */className = _ref12[2],
            propCountMatch = _ref12[3];

        if (!objectLiteralBeginMatch) {
            throw SyntaxError('Invalid input');
        }

        if (className !== 'stdClass') {
            throw SyntaxError('Unsupported object type: ' + className);
        }

        var totalOffset = objectLiteralBeginMatch.length;

        var propCount = parseInt(propCountMatch, 10);
        var obj = {};
        cache([obj]);

        str = str.substr(totalOffset);

        for (var i = 0; i < propCount; i++) {
            var prop = expectKeyOrIndex(str);
            str = str.substr(prop[1]);
            totalOffset += prop[1];

            var value = expectType(str, cache);
            str = str.substr(value[1]);
            totalOffset += value[1];

            obj[prop[0]] = value[0];
        }

        // strict parsing, expect } after object literal
        if (str.charAt(0) !== '}') {
            throw SyntaxError('Expected }');
        }

        return [obj, totalOffset + 1]; // skip final }
    }

    function expectClass(str, cache) {
        // can't be well supported, because requires calling eval (or similar)
        // in order to call serialized constructor name
        // which is unsafe
        // or assume that constructor is defined in global scope
        // but this is too much limiting
        throw Error('Not yet implemented');
    }

    function expectReference(str, cache) {
        var reRef = /^[rR]:([1-9]\d*);/;

        var _ref13 = reRef.exec(str) || [],
            _ref14 = _slicedToArray(_ref13, 2),
            match = _ref14[0],
            refIndex = _ref14[1];

        if (!match) {
            throw SyntaxError('Expected reference value');
        }

        return [cache.get(parseInt(refIndex, 10) - 1), match.length];
    }

    function expectArray(str, cache) {
        var reArrayLength = /^a:(\d+):{/;

        var _ref15 = reArrayLength.exec(str) || [],
            _ref16 = _slicedToArray(_ref15, 2),
            arrayLiteralBeginMatch = _ref16[0],
            arrayLengthMatch = _ref16[1];

        if (!arrayLengthMatch) {
            throw SyntaxError('Expected array length annotation');
        }

        str = str.substr(arrayLiteralBeginMatch.length);

        var array = expectArrayItems(str, parseInt(arrayLengthMatch, 10), cache);

        // strict parsing, expect closing } brace after array literal
        if (str.charAt(array[1]) !== '}') {
            throw SyntaxError('Expected }');
        }

        return [array[0], arrayLiteralBeginMatch.length + array[1] + 1]; // jump over }
    }

    function expectArrayItems(str) {
        var expectedItems = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
        var cache = arguments[2];

        var key = void 0;
        var hasStringKeys = false;
        var item = void 0;
        var totalOffset = 0;
        var items = [];
        cache([items]);

        for (var i = 0; i < expectedItems; i++) {
            key = expectKeyOrIndex(str);

            // this is for backward compatibility with previous implementation
            if (!hasStringKeys) {
                hasStringKeys = typeof key[0] === 'string';
            }

            str = str.substr(key[1]);
            totalOffset += key[1];

            // references are resolved immediately, so if duplicate key overwrites previous array index
            // the old value is anyway resolved
            // fixme: but next time the same reference should point to the new value
            item = expectType(str, cache);
            str = str.substr(item[1]);
            totalOffset += item[1];

            items[key[0]] = item[0];
        }

        // this is for backward compatibility with previous implementation
        if (hasStringKeys) {
            items = Object.assign({}, items);
        }

        return [items, totalOffset];
    }

    function to_unserialize(str) {
        try {
            if (typeof str !== 'string') {
                return false;
            }

            return expectType(str, initCache())[0];
        } catch (err) {
            console.error(err);
            return false;
        }
    }

    return to_unserialize(strings);
}